package vsa.migrations.junit;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import junit.framework.TestCase;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.emftext.commons.layout.KeywordLayoutInformation;
import org.emftext.commons.layout.LayoutFactory;
import org.emftext.commons.layout.LayoutInformation;
import org.emftext.language.java.annotations.AnnotationInstance;
import org.emftext.language.java.annotations.AnnotationsFactory;
import org.emftext.language.java.classifiers.Class;
import org.emftext.language.java.classifiers.ConcreteClassifier;
import org.emftext.language.java.classifiers.Interface;
import org.emftext.language.java.commons.Commentable;
import org.emftext.language.java.containers.CompilationUnit;
import org.emftext.language.java.expressions.Expression;
import org.emftext.language.java.imports.ClassifierImport;
import org.emftext.language.java.imports.Import;
import org.emftext.language.java.imports.ImportsFactory;
import org.emftext.language.java.imports.StaticMemberImport;
import org.emftext.language.java.instantiations.ExplicitConstructorCall;
import org.emftext.language.java.literals.Super;
import org.emftext.language.java.members.Constructor;
import org.emftext.language.java.members.Member;
import org.emftext.language.java.members.Method;
import org.emftext.language.java.members.impl.ClassMethodImpl;
import org.emftext.language.java.modifiers.Abstract;
import org.emftext.language.java.modifiers.AnnotableAndModifiable;
import org.emftext.language.java.modifiers.AnnotationInstanceOrModifier;
import org.emftext.language.java.modifiers.Modifier;
import org.emftext.language.java.modifiers.ModifiersFactory;
import org.emftext.language.java.modifiers.Private;
import org.emftext.language.java.modifiers.Protected;
import org.emftext.language.java.modifiers.Public;
import org.emftext.language.java.references.MethodCall;
import org.emftext.language.java.resource.java.IJavaTextPrinter;
import org.emftext.language.java.resource.java.grammar.JavaGrammarInformationProvider;
import org.emftext.language.java.resource.java.mopp.JavaResource;
import org.emftext.language.java.resource.java.util.JavaLayoutUtil;
import org.emftext.language.java.resource.java.util.JavaResourceUtil;
import org.emftext.language.java.statements.ExpressionStatement;
import org.emftext.language.java.statements.impl.ExpressionStatementImpl;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * utilities for migration
 * @author oleym
 *
 */
public class MigrationUtil {


	/**
	 * logger
	 */
	private static Logger LOGGER = Logger.getLogger(MigrationUtil.class.getName());

	/**
	 * default package for junit3
	 */
	public final static String PACKAGE_JUNIT3 = "junit.framework";
	
	
	/**
	 * cache is used as long as addStaticImport is called with the same compilationunit, 
	 * when using a new compilationunit it is cleaned to save memory
	 */
	private static HashMap<CompilationUnit, List<String>> addedImports = new HashMap<CompilationUnit, List<String>>();

	/**
	 * returns the member to add a comment to, relative to the parameterized member
	 *
	 * We try to find a method after this method and say, that the comment should be prepended (append == false)
	 * If this returns no appropriate result we try to find a method previous to this method and say, that the comment
	 * should be appended (append = true).
	 * If this returns no appropriate result we give up, because this method seems to be the only one
	 *
	 * @param clazz			clazz
	 * @param member		member to comment
	 * @return  position to add the commented member to
	 */
	public CommentAddPosition getMatchingMemberToAddComment (final Class clazz, final AnnotableAndModifiable member) {

		AnnotableAndModifiable memberToAddTo = null;
		LayoutInformation layoutInformation = null;
		final int indexOfCurrentMember = clazz.getMembers().indexOf(member);

		//try to put it to next method
		for (int i = indexOfCurrentMember +1 ; i < clazz.getMembers().size(); i++) {
			Member nextMember = clazz.getMembers().get(i);
			if (nextMember instanceof AnnotableAndModifiable) {
			  memberToAddTo = (AnnotableAndModifiable) nextMember;
			  layoutInformation = getFirstLayoutInformation(memberToAddTo);
			}
		}

		//or to classend if no method follows
		if (memberToAddTo == null)
			layoutInformation = getLastLayoutInformation(clazz);

		if (layoutInformation == null)
			throw new IllegalStateException("No appropriate layoutinformation found in class " + clazz.getName() + " for member " + member);
		else
			return new CommentAddPosition(layoutInformation, false);
	}
	
	/**
	 * returns all containers of the given layoutinfos
	 * @param infos layoutinfos
	 * @return list of containers
	 */
	public Set <Commentable> getCommentableContainers (final List<LayoutInformation> infos) {
		Set <Commentable> commentableContainers = new HashSet<Commentable>();
		for (LayoutInformation nextInfo: infos) {
			if (nextInfo.eContainer() instanceof Commentable)
				commentableContainers.add((Commentable) nextInfo.eContainer());
		}
		
		return commentableContainers;
		
	}
	
	/**
	 * normalizes the given layout
	 * Removes empty lines and adds one empty line at the beginning
	 * @param layout
	 * @param commentable
	 * @return layoutstring
	 */
	public String normalizeLayout (final Collection <String> layout, final Commentable commentable) {
		if (layout.isEmpty())
			return "\n" + getIndention(commentable);
		else {
			
			Collection<String> linePerLine = new ArrayList<String>(); 
			for (String next: layout) {
				if (next != null) {
				  String [] lineArray = next.trim().split("\n");
				  for (String nextLine: lineArray)
					if (! nextLine.trim().isEmpty())
						linePerLine.add(nextLine);
				}
			}
			
			String layoutAsString = "\n" + getIndention(commentable);
			for (String nextLine: linePerLine) {
				layoutAsString += nextLine + "\n";
			}
			
			return layoutAsString;
		}
	}

	public void comment (final Class clazz, final AnnotableAndModifiable member, final String comment) {
		CommentAddPosition commentAddPosition = getMatchingMemberToAddComment(clazz, member);

		String commentText = "";

		for (String nextComment : member.getComments())
			commentText += nextComment;

		String commentTextToAdd = "\n" + commentText + "\n/** " + comment + "\n " + JavaResourceUtil.getText(member) +  "**/";
		commentAddPosition.addComment(commentTextToAdd);
		clazz.getMembers().remove(member);
	}
	
	public List<String> getText (final EObject clazz) throws IOException {
		return Arrays.asList(getTextAsString(clazz).split("\n"));
	}
	
	public int findTextInFile (List<String> fileContent, final String text) {
		for (int i = 0 ; i < fileContent.size(); i++) {
			String next = fileContent.get(i);
			if (next.contains(text))
				return i;
		}
		
		throw new IllegalStateException("Text " + text + " was not found in file (" + fileContent + ")");
	}
	
	public String getTextAsString (final EObject clazz) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
		
		JavaResource resource = (JavaResource) clazz.eResource();
		IJavaTextPrinter printer = resource.getMetaInformation().createPrinter(bos, resource);
		JavaLayoutUtil layoutUtil = new JavaLayoutUtil();
		layoutUtil.transferAllLayoutInformationFromModel(clazz); 
		printer.print(clazz);
		
		return bos.toString();
	}
	
	public String getStrippedText (final EObject clazz) throws IOException {
		List<String> textArray = getText(clazz);
		String strippedText = ""; 
		for (String next: textArray) {
			strippedText += next.trim();
		}
		return strippedText + "\n";
	}


	/**
	 * gets fqn of an import
	 * @param nextImport import
	 * @return fqn
	 */
	public String getFqn( final Import nextImport) {
		if (nextImport instanceof ClassifierImport) {
			ClassifierImport classifierImport = (ClassifierImport) nextImport;
			return getFqn(classifierImport.getClassifier());
		}
		else if (nextImport instanceof StaticMemberImport) {
			StaticMemberImport classifierImport = (StaticMemberImport) nextImport;
			return classifierImport.getNamespacesAsString() + classifierImport.getStaticMembers().get(0).getName();
		}
		else throw new IllegalStateException("Importtype " + nextImport + " not supported");
	}

	/**
	 * checks if a member is empty
	 * @param member member
	 * @return true/false
	 */
	public boolean isEmpty (final Member member) {
		EList<ExpressionStatement> childrenByType = member.getChildrenByType(ExpressionStatement.class);
		return childrenByType.size() == 0;
	}
	
	
	
	/**
	 * returns is the class is abstract 
	 * @param clazz class to check
	 * @return true/false
	 */
	public boolean isAbstract (final Class clazz) {
		for (Modifier nextModifier: clazz.getModifiers()) {
			if (nextModifier instanceof Abstract)
				return true;
		}
		
		return false;
	}

	/**
	 * gets fqn of a class
	 * @param clazz class
	 * @return fqn
	 */
	public String getFqn (final ConcreteClassifier clazz) {
		return clazz.getContainingCompilationUnit().getNamespacesAsString() + clazz.getName();
	}

	/**
	 * gets classifier and checks if it can be resolved
	 * @param compilationUnit  current compilationunit, needed for context
	 * @param fqn  fqn of class to be resolved
	 * @return classifier
	 */
	public ConcreteClassifier getClassifier (final CompilationUnit compilationUnit, String fqn) {
	  ConcreteClassifier classifierRunWith = compilationUnit.getConcreteClassifier(fqn);
	  if (classifierRunWith.eIsProxy())
		throw new IllegalStateException("Cannot resolve proxy for class " + fqn + ". Check if class exists");
	  return classifierRunWith;
	}


	/**
	 * loginfo prepending unit-name
	 * @param unit unit
	 * @param message message to log
	 */
	public void logInfo (final CompilationUnit unit, final String message) {
		LOGGER.info("->Unit " + unit.getName() + "  :" + message);
	}

	/**
	 * gets class, if it is a testcase or <code>null</code> if it no testcase
	 * @param compilationUnit  unit
	 * @return  testcase or <code>null</code>
	 * @throws JunitMigrationException
	 */
	public Class getTestCase (final CompilationUnit compilationUnit) throws JunitMigrationException {

		if (compilationUnit.getClassifiers().size() != 1)
			throw new JunitMigrationException("There are " + compilationUnit.getClassifiers().size() + " classifiers in compilationUnit " + compilationUnit.getName() + " (expected: 1)");

		//TODO @Test-Methoden ueberpruefen

		Class classifierTestCaseJunit3 = (Class) getClassifierTestCaseJunit3(compilationUnit);

		ConcreteClassifier concreteClassifier = compilationUnit.getClassifiers().get(0);
		if (concreteClassifier instanceof Class) {
			Class clazz = (Class) concreteClassifier;
			boolean extendsTestCase = extendsClass(clazz, classifierTestCaseJunit3);
			boolean hasTestMethods = isConcreteTestclass(clazz);

			logInfo (compilationUnit, "Class " + clazz.getName() + " extends testcase: " + extendsTestCase + " hasTestMethods: " + hasTestMethods);
			if (extendsTestCase || hasTestMethods)
			  return clazz;
		}
		else
			logInfo (compilationUnit, "CompilationUnit " + compilationUnit.getName() + " is not a class, but type: " + compilationUnit.getClassifiers().get(0).getName());

		return null;
	}

	/**
	 * finds a ceratin annotation in a annotable type
	 * @param annotatableAndModifiableType annotable type to search annotation in
	 * @param classifier  annotation to find
	 * @return annotation instance
	 */
	public AnnotationInstance findAnnotation (final AnnotableAndModifiable annotatableAndModifiableType, final ConcreteClassifier classifier) {
		EList<AnnotationInstanceOrModifier> annotationInterfaces = annotatableAndModifiableType.getAnnotationsAndModifiers();
		for (AnnotationInstanceOrModifier nextAnnotationItem : annotationInterfaces) {
			if (nextAnnotationItem instanceof AnnotationInstance) {
				AnnotationInstance nextAnnotation = (AnnotationInstance) nextAnnotationItem;
				if (nextAnnotation.getAnnotation().equals(classifier))
					return nextAnnotation;
			}

		}

		return null;
	}

	/**
	 * checks if a methodcall is a supermethod-call
	 * @param clazz					class to check
	 * @param methodCall			methodcall
	 * @return true/false
	 */
	public boolean isSupermethodCall (final Class clazz, final MethodCall methodCall) {
		if (methodCall.getTarget().getContainingConcreteClassifier().equals(clazz.getSuperClass())) {
			ClassMethodImpl classmethod = (ClassMethodImpl) getContainer(methodCall, ClassMethodImpl.class);
			  return methodCall.getTarget().getName().equals(classmethod.getName());
		}

		return false;
	}

	/**
	 * gets direct or indirect container of an eobject
	 * @param object eobject
	 * @param clazz class to find
	 * @return container named with clazz
	 */
	public EObject getContainer (EObject object, final java.lang.Class<? extends EObject> clazz) {
		while (object.eContainer() != null) {
			if (object.eContainer().getClass().equals(clazz))
				return object.eContainer();
			object = object.eContainer();
		}

		return null;

	}

	/**
	 * remove all calls to super-methods in this class
	 * @param clazz  class to remove the supercalls
	 */
	public void removeSuperCalls (final Class clazz) {

		List <ExpressionStatement> statementsToRemove = new ArrayList<ExpressionStatement>();

		for (Iterator<EObject> it = clazz.eAllContents(); it.hasNext();) {
			EObject nextObject = it.next();

			//System.out.println("Class " + clazz.getName() + " Member: " + nextObject);
			if (nextObject instanceof ExplicitConstructorCall) {
				ExplicitConstructorCall explicitConstructorCall = (ExplicitConstructorCall) nextObject;
				if (explicitConstructorCall.getCallTarget() instanceof Super) {
					ExpressionStatement statement = (ExpressionStatement) getContainer(explicitConstructorCall, ExpressionStatementImpl.class);
					statementsToRemove.add(statement);
					if (LOGGER.isLoggable(Level.FINE))
					  LOGGER.fine("Remove supercall of constructor (" + nextObject.eContainer() + ")");
				}
			}

			if (nextObject instanceof MethodCall) {
			  MethodCall methodCall = (MethodCall) nextObject;
			  if (isSupermethodCall(clazz, methodCall)) {
				ExpressionStatement statement = (ExpressionStatement) getContainer(methodCall, ExpressionStatementImpl.class);
				statementsToRemove.add(statement);
				 if (LOGGER.isLoggable(Level.FINE))
				  LOGGER.fine("Supercall of method found (" + methodCall.eContainer().eContainer().eContainer().eContainer() + ")" + methodCall.getTarget());
			  }
			}
		}

		for (ExpressionStatement nextStatement : statementsToRemove) {
			EcoreUtil.remove(nextStatement);
		}
	}

	/**
	 * find a method with a certain name in the classifier
	 * @param clazz    classifier to search method in
	 * @param methodname name of the method to find
	 * @return method or <code>null</code> if method was not found
	 */
	public Method findMethod (final ConcreteClassifier clazz, final String methodname) {
		for (Method nextMethod: clazz.getMethods()) {
			if (nextMethod.getName().equals(methodname))
				return nextMethod;
		}

		return null;

	}

	/**
	 * find a class in resourceset
	 * @param rs  resourceset
	 * @param classname  simple name of class
	 * @return interface
	 */
	public Class findClass (final ResourceSet rs, final String classname) {
		for (Resource nextResource: rs.getResources()) {
			if (nextResource.getURI().toFileString() != null && nextResource.getURI().toFileString().endsWith(classname + ".java")) {
				EObject eObject = nextResource.getContents().get(0);
				if (eObject instanceof CompilationUnit) {
					CompilationUnit compilationUnit = (CompilationUnit) eObject;
					return (Class) compilationUnit.getClassifiers().get(0);
				}
			}
		}

		throw new IllegalStateException("No class " + classname + " found in resourceset");

	}

	/**
	 * find a interface in resourceset
	 * @param rs  resourceset
	 * @param classname  simple name of interface
	 * @return interface
	 */
	public Interface findInterface (final ResourceSet rs, final String classname) {
		for (Resource nextResource: rs.getResources()) {
			if (nextResource.getURI().toFileString() != null && nextResource.getURI().toFileString().endsWith(classname + ".java")) {
				EObject eObject = nextResource.getContents().get(0);
				if (eObject instanceof CompilationUnit) {
					CompilationUnit compilationUnit = (CompilationUnit) eObject;
					return (Interface) compilationUnit.getClassifiers().get(0);
				}
			}
		}
		

		throw new IllegalStateException("No class " + classname + " found in resourceset");

	}

	/**
	 * checks if the constructor only contains a supercall
	 * @param constructor constructor
	 * @return true/false
	 */
	public boolean callsOnlySuperDefaultConstructor (final Constructor constructor)  {
		EList<Expression> childrenByType = constructor.getChildrenByType(Expression.class);
		
		if (childrenByType.size() == 0) 
			return true;
		
		if (childrenByType.size() == 1) {
			return childrenByType.get(0) instanceof ExplicitConstructorCall;
		}

		return false;
	}


	/**
	 * get default-constructor of class or <code>null</code>
	 * @param clazz       class to get the constrctor from
	 * @param onlyPublic  true: only public: false: even public, private and packageprivate constructors
	 * @return default constructor
	 */
	public Constructor getDefaultConstructor (final Class clazz, final boolean onlyPublic) {
		for (Constructor next: getConstructors(clazz)) {
			if (next.getParameters().size() == 0)
				if (! onlyPublic || next.isPublic())
				  return next;
		}

		return null;
	}

	/**
	 * get list of constructors of the class
	 * @param clazz class to get constructors from
	 * @return list of constructors
	 */
    public List <Constructor> getConstructors (final Class clazz) {

		List <Constructor> constructors = new ArrayList<Constructor>();

		for (Member nextMember: clazz.getMembers()) {
			if (nextMember instanceof Constructor)  {
				constructors.add((Constructor) nextMember);
			}
		}

		return constructors;
	}

    /**
	 * gets classifier of junit3-TestCase
	 * @param compilationunit compilationunit as context
	 * @return classifier
	 */
	public ConcreteClassifier getClassifierTestCaseJunit3(CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier(TestCase.class.getName());
	}

	/**
	 * gets classifier of Object
	 * @param compilationunit compilationunit as context
	 * @return classifier
	 */
	public ConcreteClassifier getClassifierObject(CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier(Object.class.getName());
	}

	/**
	 * gets classifier of RunWith
	 * @param compilationunit compilationunit as context
	 * @return classifier
	 */
	public ConcreteClassifier getClassifierRunwith(CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier(RunWith.class.getName());
	}

	/**
	 * gets classifier of Test
	 * @param compilationunit compilationunit as context
	 * @return classifier
	 */
	public ConcreteClassifier getClassifierTest(CompilationUnit compilationunit) {
		ConcreteClassifier classifier = compilationunit.getConcreteClassifier(Test.class.getName());
		
		if (classifier.eIsProxy())
			throw new IllegalStateException("Classifier " + classifier.getName() + " is a proxy and cannot be resolved. Please check if you have junit4 in your classpath");
		
		return classifier; 
	}



	/**
	 * gets classifier of Before
	 * @param compilationunit compilationunit as context
	 * @return classifier
	 */
	public ConcreteClassifier getClassifierBefore(CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier(Before.class.getName());
	}

	/**
	 * gets classifier of After
	 * @param compilationunit compilationunit as context
	 * @return classifier
	 */
	public ConcreteClassifier getClassifierAfter(CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier(After.class.getName());
	}

	/**
	 * gets classifier of Assert
	 * @param compilationunit compilationunit as context
	 * @return classifier
	 */
	public ConcreteClassifier getClassifierAssert(CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier(Assert.class.getName());
	}

	public Modifier getModifier (final AnnotableAndModifiable annotatbleAndModifiable) {
		for (AnnotationInstanceOrModifier next: annotatbleAndModifiable.getAnnotationsAndModifiers()) {
			if (next instanceof Modifier)
				return (Modifier) next;
		}

		return null;
	}


	
	

	/**
	 * adds an annotation to the annotatable element, if not yet done
	 * @param annotatableAndModifiableType  annotatable element
	 * @param classifier   annotation to annotate
	 * @return annotation itself
	 */
	public AnnotationInstance addAnnotationOnDemand (final AnnotableAndModifiable annotatableAndModifiableType, final ConcreteClassifier classifier) {

		AnnotationInstance assignedAnnotation = findAnnotation(annotatableAndModifiableType, classifier);
		if (assignedAnnotation == null) {
		  assignedAnnotation = AnnotationsFactory.eINSTANCE.createAnnotationInstance();
		  assignedAnnotation.setAnnotation(classifier);

		  LayoutInformation layoutInfoAt = LayoutFactory.eINSTANCE.createKeywordLayoutInformation();
		  layoutInfoAt.setSyntaxElementID(JavaGrammarInformationProvider.getSyntaxElementID(JavaGrammarInformationProvider.JAVA_12_0_0_0));
		  layoutInfoAt.setVisibleTokenText("@");

		  LayoutInformation layoutInfoAnnotationName = LayoutFactory.eINSTANCE.createKeywordLayoutInformation();
		  layoutInfoAnnotationName.setSyntaxElementID(JavaGrammarInformationProvider.getSyntaxElementID(JavaGrammarInformationProvider.JAVA_12_0_0_2));
		  layoutInfoAnnotationName.setVisibleTokenText(classifier.getName());

		  //collect all layoutinformations that have to be moved to the new annotation ( infos at existing annotations and modifiers)
		  List <LayoutInformation> layoutInfosToMove = new ArrayList<LayoutInformation>();
		  for (AnnotationInstanceOrModifier modifier: annotatableAndModifiableType.getAnnotationsAndModifiers()) 
			  layoutInfosToMove.addAll(modifier.getLayoutInformations());
		  
		  //add text of all previous layoutinfos to layoutinfo of the new annotation
		  List<String> layout = new ArrayList<String>();
		  for (LayoutInformation nextLayoutInfo: layoutInfosToMove) {
			  layout.add(nextLayoutInfo.getHiddenTokenText());
		  }
		  
		  //remove all layoutinformations at old positions  
		  for (Commentable nextContainer : getCommentableContainers(layoutInfosToMove)) {
			  if (nextContainer.getLayoutInformations().size() > 1) {
				  for (int i = nextContainer.getLayoutInformations().size() - 1; i >= 1; i--)
					  nextContainer.getLayoutInformations().remove(i);
			  }
			  if (nextContainer.getLayoutInformations().size() > 0)
				  nextContainer.getLayoutInformations().get(0).setHiddenTokenText("\n" + getIndention(annotatableAndModifiableType));
		  }

		  //if no hidden text is moved from outside, we add a newline to have the annotation in a seperate line 
		  layoutInfoAt.setHiddenTokenText(normalizeLayout(layout, annotatableAndModifiableType));

		  if (assignedAnnotation.getLayoutInformations().size() == 0) {
			  assignedAnnotation.getLayoutInformations().add(layoutInfoAt);
			  assignedAnnotation.getLayoutInformations().add(layoutInfoAnnotationName);
		  }
		  //move comment to annotation

		  annotatableAndModifiableType.getAnnotationsAndModifiers().add(0, assignedAnnotation);
		  addImportOnDemand(annotatableAndModifiableType.getContainingConcreteClassifier().getContainingCompilationUnit(), classifier);

		}

		return assignedAnnotation;
	}

	/**
	 * checks if the class extends the extendedClass directly or indirectly
	 *
	 * @param clazz			class
	 * @param extendedClass	class, that is checked to be extended
	 * @return true/false
	 * @throws JunitMigrationException
	 */
	public boolean extendsClass (Class clazz, final Class extendedClass) throws JunitMigrationException {
		while (! clazz.getName().equals("Object")){
			if (clazz.getSuperClass() == null)
				throw new JunitMigrationException("Superclass of class " + clazz + " is null, but must be at least Object");

			if (clazz.equals(extendedClass) || clazz.getSuperClass().equals(extendedClass))
				return true;

			clazz = clazz.getSuperClass();
		}

		return false;
	}



	/**
	 * adds a class import if not yet exists in unit
	 * @param unit		  compilationunit to add import to
	 * @param classifier  classifier to import
	 */
	public void addImportOnDemand (final CompilationUnit unit, final ConcreteClassifier classifier) {

		if (findImport(unit, classifier) == null) {
			if (LOGGER.isLoggable(Level.FINE))
				  LOGGER.fine("->Unit " + unit.getName() + ": Add import " + classifier.getName());
		  ClassifierImport classimport = ImportsFactory.eINSTANCE.createClassifierImport();
		  classimport.getNamespaces().addAll(classifier.getContainingPackageName());
		  classimport.setClassifier(classifier);
		  unit.getImports().add(classimport);
		}
	}
	
	private String getImportStaticKey (final ConcreteClassifier classifier, final Method method) {
		return classifier.getContainingPackageName().toString() + "." + classifier.getName() + "#" + method.getName();
	}

	/**
	 * adds a static import if not yet exists in unit
	 * @param unit		  compilationunit to add import to
	 * @param classifier  classifier of static import
	 * @param method	  method of static import
	 */
	public void addStaticImportOnDemand (final CompilationUnit unit, final ConcreteClassifier classifier, final Method method) {
		
		String staticImportKey = getImportStaticKey(classifier, method);
		if (addedImports.get(unit) == null)
			addedImports.clear(); 
		
		List<String> list = addedImports.get(unit);
		if (list == null) {
			list = new ArrayList<String>();
			addedImports.put(unit, list);
		}
		if (list.contains(staticImportKey))
			return;
		
		if (LOGGER.isLoggable(Level.FINE))
		  LOGGER.fine("->Unit " + unit.getName() + ": Add static import " + staticImportKey);

		if (findStaticImport(unit, classifier, method) == null) {
		  StaticMemberImport staticMemberImport = ImportsFactory.eINSTANCE.createStaticMemberImport();
		  staticMemberImport.setStatic(ModifiersFactory.eINSTANCE.createStatic());
		  staticMemberImport.getNamespaces().addAll(classifier.getContainingPackageName());
		  staticMemberImport.getNamespaces().add(classifier.getName());
		  staticMemberImport.getStaticMembers().add(method);
		  unit.getImports().add(staticMemberImport);
		}
		
		addedImports.get(unit).add(staticImportKey); //Add import to cache because it was available before or we have added it now		
	}

	/**
	 * checks if the cals is concrete classtest,
	 * which means it contains junit3 or junit4 testmethods
	 * @param clazz classname
	 * @return true/false
	 */
	public boolean isConcreteTestclass (final Class clazz) {
		for (Method method : clazz.getMethods()) {
			boolean annotationAvailable = findAnnotation(method, getClassifierTest(clazz.getContainingCompilationUnit())) != null;
			if (isTestMethod(method) || annotationAvailable)
				return true;
		}

		return false;
	}

	/**
	 * checks if the method is a testmethod
	 * - name starts with test
	 * - public and static
	 * - returns void
	 *
	 * @param method method
	 * @return true/false
	 */
	public boolean isTestMethod (final Method method) {
		return method.isPublic() && ! method.isStatic() && method.getName().startsWith("test") && method.getTypeReference() instanceof org.emftext.language.java.types.Void;
	}

	/**
	 * gets a conrecte class import found in the compilationunit, if available or <code>null</code>
	 * @param compilationunit  compilationunit to find import in
	 * @param importedClass  imported class
	 * @return import or <code>null</code> if import was not found
	 */
	public Import findImport (final CompilationUnit compilationunit, final ConcreteClassifier importedClass) {
		for (Import nextImport: compilationunit.getImports()) {
			if (nextImport instanceof ClassifierImport) {
				ClassifierImport classifierImport = (ClassifierImport) nextImport;
				if (classifierImport.getClassifier().equals(importedClass))
					return nextImport;
			}
		}

		return null;
	}

	/**
	 * gets a static import found in the compilationunit, if available or <code>null</code>
	 * @param compilationunit  compilationunit to find import in
	 * @param classifier classifier which contains the importable method
	 * @param importedMethod imported method
	 * @return import or <code>null</code> if import was not found
	 */
	public Import findStaticImport (final CompilationUnit compilationunit, ConcreteClassifier classifier, final Method importedMethod) {
		String fqn = getFqn(classifier) + "$" + importedMethod.getName();
		for (Import nextImport: compilationunit.getImports()) {
			if (nextImport instanceof StaticMemberImport) {
				String fqnImport = getFqn(nextImport);

				if (fqnImport.equals(fqn))
					return nextImport;
			}
		}

		return null;
	}

	/**
	 * removes all imports that start with a certain package
	 * @param compilationunit
	 */
	public void removeImports (CompilationUnit compilationunit, String packageImport) {
		Collection <Import> removedImports = new ArrayList<Import>();
		for (Import nextImport: compilationunit.getImports()) {
			String namespacesAsString = nextImport.getNamespacesAsString();
			if (namespacesAsString.startsWith(packageImport))
				removedImports.add(nextImport);
		}

		compilationunit.getImports().removeAll(removedImports);
	}

	


	public List<LayoutInformation> getRecursiveLayoutInfos (final Commentable commentable) {

		List <LayoutInformation> layoutInfos = new ArrayList<LayoutInformation>();


		Collection <EObject> objects = new ArrayList<EObject>();
		objects.add(commentable);
		TreeIterator<Object> allContents = EcoreUtil.getAllContents(objects);
		while (allContents.hasNext()) {
			EObject next = (EObject) allContents.next();
			if (next instanceof Commentable ) {
				Commentable childCommentable = (Commentable) next;
				layoutInfos.addAll(childCommentable.getLayoutInformations());
			}
		}

		layoutInfos.addAll(commentable.getLayoutInformations());

		Collections.sort(layoutInfos, new Comparator<LayoutInformation>() {

			@Override
			public int compare(LayoutInformation o1, LayoutInformation o2) {
				return o1.getStartOffset() - o2.getStartOffset();

			}
		});

		return layoutInfos;
	}

	public String getIndention (final Commentable commentable) {
		LayoutInformation lastLayoutInformation = getLastLayoutInformation(commentable);
		String tokenText = lastLayoutInformation.getHiddenTokenText();
		String [] splittedTokenText = tokenText.split("\n");
		if (splittedTokenText.length == 0)
			return "";
		else
			return splittedTokenText [splittedTokenText.length - 1];
	}

	public LayoutInformation getLastLayoutInformation (final Commentable commentable) {
		List <LayoutInformation> layoutInfos = getRecursiveLayoutInfos(commentable);
		if (layoutInfos.isEmpty())
		  return null;
		else
		  return layoutInfos.get(layoutInfos.size() - 1);
	}

	public LayoutInformation getFirstLayoutInformation (final Commentable commentable) {
		List <LayoutInformation> layoutInfos = getRecursiveLayoutInfos(commentable);
		if (layoutInfos.isEmpty())
		  return null;
		else
		  return layoutInfos.get(0);
	}

	public LayoutInformation createModifierLayout (final Method method, final Modifier modifier) {
		KeywordLayoutInformation layoutInformation = LayoutFactory.eINSTANCE.createKeywordLayoutInformation();

		if (modifier instanceof Public)
			layoutInformation.setSyntaxElementID(JavaGrammarInformationProvider.getSyntaxElementID(JavaGrammarInformationProvider.JAVA_122_0_0_0));
		else if (modifier instanceof Protected)
			layoutInformation.setSyntaxElementID(JavaGrammarInformationProvider.getSyntaxElementID(JavaGrammarInformationProvider.JAVA_124_0_0_0));
		else if (modifier instanceof Private) {

		}
		else LOGGER.warning("Modifier " + modifier + " is not yet supported to be layouted");

		layoutInformation.setVisibleTokenText(JavaResourceUtil.getText(modifier));
		layoutInformation.setHiddenTokenText("\n" + getIndention(method));

		return layoutInformation;

	}

	public void normalizelayout(Method nextMethod) {

		if (nextMethod.getModifiers().size() > 0) {
			//LayoutInformation newLayoutInformation = createModifierLayout(nextMethod, nextMethod.getModifiers().get(0));
			//nextMethod.getModifiers().get(0).getLayoutInformations().add(newLayoutInformation);
		}
		else
			 LOGGER.warning("Method " + nextMethod.getName() + " has no modifier, normalizing layout is currently not possible in this scenario");

	}

}
